import {
    ApplicationOptions,
    InterfacePainterAPI,
    ApplicationEvent,
    FxItem,
} from "../interface/Application.interface"
import { FileType, HighlightType, RenderMode } from "../interface"

import { initCanvas } from "./func/initCanvas"
import { updateView } from "./func/updateView"
import { mouseDown } from "./func/mousedown"
import { mouseMove } from "./func/mousemove"
import { mouseUp } from "./func/mouseup"
import { mouseWheel } from "./func/mouseWheel"
import { mouseLeave } from "./func/mouseLeave"
import { resetColor } from "./func/resetColor"
import { initEventListener } from "./func/initEventListener"

import { createOptimizedImageRenderer } from "./webgpuDraw"
import { IO, throttle } from "stl-typescript"
import { Connector } from "cax-connector"
import { Selector } from "./selector"
import { boxSelection } from "./func/boxSelection"
import { CaxData } from "cax-data"
import { v7 as uuid } from "uuid"

export class Application {
    container: HTMLElement
    private canvas: HTMLCanvasElement = document.createElement("canvas")
    private painter: InterfacePainterAPI
    private connector = new Connector()
    private options: ApplicationOptions
    private mouseMoveThrottle = throttle(mouseMove, 20)
    private selector: Selector
    private innerContainer: HTMLElement
    private connectCallBackQueue: Array<() => void> = []
    private db = new CaxData()

    hook = {
        onLoad: (callback: () => void) => {
            IO.on(ApplicationEvent.APPLICATION_LOAD, callback)
        },
        onConnect: (callback: () => void) => {
            if (this.connectCallBackQueue.findIndex((item) => item === callback) !== -1) {
                return
            }
            this.connectCallBackQueue.push(callback)
        },
    }

    view = {
        update: () => {
            this.cmd.view.resize(
                this.container.clientWidth,
                this.container.clientHeight
            )
            updateView(this.connector)
        },
        resize: () => {
            this.canvas.width = this.container.clientWidth
            this.canvas.height = this.container.clientHeight
            this.view.update()
        },
    }

    cmd = {
        file: {
            load: (path: string, type: FileType) => {
                return this.connector.request("command", "cmd.file.load", {
                    path: path,
                    FileType: type,
                })
            },
        },
        attribute: {
            list: () => {
                return this.connector.request(
                    "command",
                    "cmd.attribute.list",
                    {}
                )
            },
            get: (attribute: string) => {
                return this.connector
                    .request("command", "cmd.attribute.get", {
                        attribute: attribute,
                    })
                    .then((res: any) => {
                        if (res.Result.DataPath) {
                            return fetch(res.Result.DataPath)
                        } else {
                            return Promise.resolve(null)
                        }
                    })
                    .then((response: any) => response.text())
                    .then((res) => {
                        return Promise.resolve(JSON.parse(res))
                    })
            },
        },
        render: {
            attribute: (attribute: string) => {
                return this.connector.request(
                    "command",
                    "cmd.render.attribute",
                    {
                        attribute: attribute,
                    }
                )
            },
            mode: (mode: RenderMode) => {
                return this.connector.request("command", "cmd.render.mode", {
                    mode: mode,
                })
            },
            clear: () => {
                return this.connector.request("command", "cmd.render.clear", {})
            },
            highlight: (
                mark: number | string,
                type: HighlightType = HighlightType.BLOCK,
                color?: [number, number, number, number]
            ) => {
                let s = {
                    type: type,
                    color: color || [255, 0, 0, 255],
                }
                if (type === HighlightType.BLOCK) {
                    s["bid"] = mark
                } else if (type === HighlightType.CELL_TYPE) {
                    s["CellType"] = mark
                }
                return this.connector.request(
                    "command",
                    "cmd.render.highlight",
                    s,
                )
            },
            resetHighlight: () => {
                return this.connector.request(
                    "command",
                    "cmd.render.highlight.reset",
                    {}
                )
            },
            show: (list: string[]) => {
                return this.connector.request(
                    "command",
                    "cmd.render.show",
                    { list: list }
                )
            }
        },
        view: {
            resize: (width, height) => {
                return this.connector.request("command", "cmd.view.resize", {
                    width: width,
                    height: height,
                })
            },
        },
        topology: {
            get: () => {
                return this.connector.request("command", "cmd.topology.get", {})
            },
        },
        actor: {
            color: {
                reset: (uid?: string) => {
                    return resetColor(this.connector, uid)
                },
            },
        },
        mesh: {
            list: () => {
                return this.connector.request("command", "cmd.mesh.list", {})
            },
        },
        fx: {
            run: (code: string) => {
                return this.connector.request("command", "cmd.fx.run", {
                    code: code,
                })
            },
        }
    }

    fx = {
        list: (): FxItem[] => {
            return this.db.local.getItem("fx") || Promise.resolve([])
        },
        clear: () => {
            return this.db.local.setItem("fx", [])
        },
        add: async (fx: FxItem) => {
            fx.id = uuid()
            let list = await this.fx.list()
            if (list === null) {
                list = []
            }
            list.push(fx)
            return this.db.local.setItem("fx", list)
        },
        remove: async (id: string) => {
            let list = await this.fx.list()
            list = list.filter((item) => item.id !== id)
            return this.db.local.setItem("fx", list)
        },
        update: async (id: string, fx: FxItem) => {
            let list = await this.fx.list()
            list = list.map((item) => {
                if (item.id === id) {
                    return fx
                } else {
                    return item
                }
            })
            return this.db.local.setItem("fx", list)
        }
    }

    constructor(dom: HTMLElement, options: ApplicationOptions) {
        this.db.initLocal()
        this.container = dom
        this.innerContainer = document.createElement("div")
        this.innerContainer.style.width = "100%"
        this.innerContainer.style.height = "100%"
        this.container.appendChild(this.innerContainer)
        this.innerContainer.appendChild(this.canvas)
        this.options = options
        if (!options.testMode) {
            createOptimizedImageRenderer(this.canvas).then((painter) => {
                this.painter = painter
                IO.emit(ApplicationEvent.APPLICATION_LOAD, 1)
            })
        }

        this.selector = new Selector(this.canvas, this.innerContainer)
        this.selector.selectBox.onchange = (x1, y1, x2, y2) => {
            boxSelection(this.connector, x1, y1, x2, y2)
        }
        this.connector.connectCallBack = () => {
            this.connectCallBackQueue.forEach((callback) => {
                callback()
            })
        }
    }

    draw(url: string) {
        this.painter.renderImage(url)
    }

    connect(url: string, path: string) {
        this.connector.connect("engine_client", url, {
            path: path || "/socket.io",
        })
        initEventListener(this.connector, this)
        initCanvas(
            this.canvas,
            this.container.clientWidth,
            this.container.clientHeight,
            (ev) => {
                mouseDown(this.connector, ev)
            },
            (ev) => {
                this.mouseMoveThrottle(this.connector, ev)
            },
            (ev) => {
                mouseUp(this.connector, ev)
            },
            (ev) => {
                mouseWheel(this.connector, ev)
            },
            (ev) => {
                mouseLeave(this.connector, ev)
            }
        )
    }

    quickCmd(type: string, params: any) {
        return this.connector.request("command", type, params)
    }
}
